#ifdef SU1
#define _GLIBCXX_DEBUG
#endif

#include <algorithm>
#include <bitset>
#include <cassert>
#include <climits>
#include <cstring>
#include <cstdio>
#include <ctime>
#include <cstdlib>
#include <cmath>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <stack>
#include <set>
#include <string>
#include <utility>
#include <vector>

using namespace std;

#define forn(i, n) for (int i = 0; i < int(n); i++)
#define forl(i, n) for (int i = 1; i <= int(n); i++)
#define ford(i, n) for (int i = int(n) - 1; i >= 0; i--)
#define fore(i, l, r) for (int i = int(l); i <= int(r); i++)
#define pb(a) push_back(a)
#define mp(x, y) make_pair((x), (y))
#define sz(a) (int) (a).size()
#define all(a) (a).begin(), (a).end()
#define ft first
#define sc second
#define x first
#define y second

template<typename X> inline X abs(const X& a) { return a < 0 ? -a : a; }
template<typename X> inline X sqr(const X& a) { return a * a; }

typedef long long li;
typedef long double ld;
typedef pair<int, int> pt;

const int INF = int(1e9);
const li INF64 = li(1e18);
const ld PI = acosl(ld(-1));
const ld EPS = 1e-9;

const int N = 2050;
int n, m;
vector<pt> g[N];

inline bool read()
{
	assert(scanf("%d%d", &n, &m) == 2);
	forn(i, m) {
		int a, b;
		assert(scanf("%d%d", &a, &b) == 2);
		a--; b--;
		g[a].pb(pt(b, i));
		g[b].pb(pt(a, i));
	}
	return true;
}

vector<pt> gDown[N];
vector<int> gUp[N];
int tin[N], curt = 0;
bool used[N];
void dfs(int v, int pe = -1) {
	tin[v] = curt++;
	forn(i, sz(g[v])) {
		if (g[v][i].y == pe) continue;
		int to = g[v][i].x;
		if (used[to]) {
			if (tin[to] < tin[v])
				gUp[v].pb(to);
		} else {
			gDown[v].pb(pt(to, g[v][i].y));
			used[to] = true;
			dfs(to, g[v][i].y);
		}
	}
}

int fup[N];
li ans = 0;
const int M = 100500;
bool delE[M];
bool isBr2[M];
bool isBr[M];
bool calcBr;
vector<pt> g2[N];
bool isDown[M];
int curDel = -1;

void dfs2(int v, int pe = -1) {
	tin[v] = fup[v] = curt++;
	forn(i, sz(g2[v])) {
		int to = g2[v][i].x;
		if (g2[v][i].y == pe) continue;
		if (delE[g2[v][i].y]) continue;
		if (used[to])
			fup[v] = min(fup[v], tin[to]);
		else {
			used[to] = true;
			dfs2(to, g2[v][i].y);
//			cerr << v + 1 << ' ' << to + 1 << ' ' << fup[to] << ' ' << tin[v] << endl;
			if (fup[to] > tin[v] && (calcBr || !isBr[g2[v][i].y]) && (!isDown[g2[v][i].y] || g2[v][i].y > curDel)) {
				isBr2[g2[v][i].y] = true;
				ans++;
			}
			fup[v] = min(fup[v], fup[to]);
		}
	}
}

inline void solve()
{
	forn(i, n)
		forn(j, sz(g[i]))
			g2[i].pb(g[i][j]);
	used[0] = true;
	dfs(0);
	forn(v, n) {
		forn(i, sz(gDown[v]))
			isDown[gDown[v][i].y] = true;
	}
  	forn(i, n)
  		used[i] = false;
  	used[0] = true;
  	calcBr = true;
	dfs2(0);
	calcBr = false;
	li cntBr = ans;
	ans = cntBr * (m - 1) - cntBr * (cntBr - 1) / 2;
	forn(i, m)
		isBr[i] = isBr2[i];
	forn(v, n) {
		forn(i, sz(gDown[v])) {
			if (isBr[gDown[v][i].y]) continue;
			delE[gDown[v][i].y] = true;
			forn(j, n)
				used[j] = false;
			used[0] = true;
			curDel = gDown[v][i].y;
			if (clock() > 950) {
				cout << ans << endl;
				exit(0);
			}
			dfs2(0);
			delE[gDown[v][i].y] = false;
		}
	}
	cout << ans << endl;
}

int main()
{
#ifdef SU1
	assert(freopen("input.txt", "rt", stdin));
//	assert(freopen("output.txt", "wt", stdout));
#endif

	cout << fixed << setprecision(10);
	cerr << fixed << setprecision(5);

	assert(read());
	solve();
	
#ifdef SU1
	cerr << "=== TIME : " << clock() << " ===" << endl;
#endif
	return 0;
}
